package com.googlecode.afx.spring;

import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;

import com.googlecode.afx.annotation.AFXController;
import com.googlecode.afx.annotation.AFXInitBehavior;
import com.googlecode.afx.annotation.AFXView;
import com.googlecode.afx.utils.ReflectionUtils;
import com.googlecode.afx.view.AbstractView;

/**
 * <code>BeanPostProcessor</code> responsible for initializing JavaFX
 * views, derived from <code>View</code>. Only annotations on class that are
 * annotated with the <code>AFXController</code> annotation are considered.
 * 
 * @author MartinKoster
 *
 */
public class AFXControllerPostProcessor implements BeanPostProcessor,
		InitializingBean {

	private static final Log LOG = LogFactory.getLog(AFXControllerPostProcessor.class);
	
	private AFXViewInitializer jfxViewInitializer;

	/**
	 * Evaluates, whether the initialized Spring bean carries a <code>AFXController</code>
	 * annotation. If so, all views defined by the <code>AFXView</code>
	 * annotation are post-processed.
	 */
	@Override
	public Object postProcessAfterInitialization(Object bean, String name)
			throws BeansException {

		// extract the annotation
		AFXController jfxController = AnnotationUtils.findAnnotation(
				bean.getClass(), AFXController.class);

		if (jfxController != null) {

			AFXView jfxView = AnnotationUtils.findAnnotation(bean.getClass(), AFXView.class);

			if (jfxView != null) {

				// lookup the correct view
				AbstractView view = (AbstractView) this.jfxViewInitializer.lookupComponent(jfxView.id());

				// initialize internal fields (e.g. load the FXML from file)
				this.jfxViewInitializer.postProcessView(view, bean);
				
				// init view behavior
				this.initBehavior(bean, view);
			
			}
		}

		return bean;
	}
	
	/**
	 * Initialize the view's behavior, in case the controller has a <tt>AFXInitBehavior</tt> annotation.
	 * 
	 * @param cotnroller
	 * @param view
	 */
	protected void initBehavior(Object controller, AbstractView view) {
		Method initBehaviorMethod = ReflectionUtils.findAnnotatedMethod(controller.getClass(), AFXInitBehavior.class);
		if(initBehaviorMethod == null) {
			LOG.debug("Controller '" + controller.getClass().getName() + "' has no AFXInitBehavior annotation. Skip view behavior initialization.");
			return;
		}		 
		
		LOG.debug("Controller '" + controller.getClass().getName() + "' has AFXInitBehavior annotation. Invoking method '" + initBehaviorMethod.getName() + "'.");
		
		Class<?>[] parameterTypes = initBehaviorMethod.getParameterTypes();
		Object[] args = ReflectionUtils.matchInstancesToParameterTypes(parameterTypes, controller, view, view.getViewStateManager(), view.getViewCache(), view.getValidationHandler());
		
		ReflectionUtils.invokeMethod(initBehaviorMethod, controller, args);
		
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String name)
			throws BeansException {
		return bean;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if (this.jfxViewInitializer == null) {
			throw new FatalBeanException(
					"Property 'jfxViewFactory' must be initialized!");
		}
	}

	public AFXViewInitializer getJfxViewInitializer() {
		return jfxViewInitializer;
	}

	public void setJfxViewInitializer(AFXViewInitializer jfxViewInitializer) {
		this.jfxViewInitializer = jfxViewInitializer;
	}
}
